package com.sgcc.nfc.lock.core.nfc

import android.nfc.tech.NfcA
import android.util.Log
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

object NfcRawHelper {

    private const val TAG = "NfcRawHelper"

    suspend fun start(nfcA: NfcA): Boolean = withContext(Dispatchers.IO) {
        runCatching {
            val response = nfcA.transceive(byteArrayOf(0x47, 0x11))
            response.size == 2 && response[0] == 0x47.toByte() && response[1] == 0x47.toByte()
        }.onFailure {
            Log.e(TAG, "start failed", it)
        }.getOrDefault(false)
    }

    suspend fun getStartAddress(nfcA: NfcA): Int? = withContext(Dispatchers.IO) {
        runCatching {
            val response = nfcA.transceive(byteArrayOf(0x60, 0x00, 0x00, 0x00, 0x00))
            if (response.size == 6 && response[0] == 0x60.toByte() && response[5] == 0x60.toByte()) {
                val b1 = response[1].toInt() and 0xFF
                val b2 = response[2].toInt() and 0xFF
                val b3 = response[3].toInt() and 0xFF
                val b4 = response[4].toInt() and 0xFF
                (b1 shl 24) or (b2 shl 16) or (b3 shl 8) or b4
            } else {
                null
            }
        }.onFailure {
            Log.e(TAG, "getStartAddress failed", it)
        }.getOrNull()
    }

    suspend fun read(nfcA: NfcA, startAddress: Int, count: Int): List<Int>? = withContext(Dispatchers.IO) {
        val values = mutableListOf<Int>()
        var address = startAddress
        repeat(count) {
            val command = byteArrayOf(
                0x80.toByte(),
                ((address ushr 24) and 0xFF).toByte(),
                ((address ushr 16) and 0xFF).toByte(),
                ((address ushr 8) and 0xFF).toByte(),
                (address and 0xFF).toByte()
            )
            val response = try {
                nfcA.transceive(command)
            } catch (ioe: IOException) {
                Log.e(TAG, "read transceive failed", ioe)
                return@withContext null
            }
            if (response.size == 6 && response[0] == 0x80.toByte() && response[5] == 0x80.toByte()) {
                val v1 = response[1].toInt() and 0xFF
                val v2 = response[2].toInt() and 0xFF
                val v3 = response[3].toInt() and 0xFF
                val v4 = response[4].toInt() and 0xFF
                val value = (v1 shl 24) or (v2 shl 16) or (v3 shl 8) or v4
                values.add(value)
                address += 4
            } else {
                return@withContext null
            }
        }
        values
    }

    suspend fun write(nfcA: NfcA, startAddress: Int, fnIndex: Int, content: List<Int>): Boolean = withContext(Dispatchers.IO) {
        var address = startAddress
        val fullContent = buildList {
            add(0x60000000 or (fnIndex and 0xFF))
            addAll(content)
        }

        for (value in fullContent) {
            val command = byteArrayOf(
                0x70.toByte(),
                ((address ushr 24) and 0xFF).toByte(),
                ((address ushr 16) and 0xFF).toByte(),
                ((address ushr 8) and 0xFF).toByte(),
                (address and 0xFF).toByte(),
                ((value ushr 24) and 0xFF).toByte(),
                ((value ushr 16) and 0xFF).toByte(),
                ((value ushr 8) and 0xFF).toByte(),
                (value and 0xFF).toByte()
            )
            val response = try {
                nfcA.transceive(command)
            } catch (ioe: IOException) {
                Log.e(TAG, "write transceive failed", ioe)
                return@withContext false
            }
            if (response.size != 2 || response[0] != 0x70.toByte() || response[1] != 0x70.toByte()) {
                return@withContext false
            }
            address += 4
        }

        val completeCommand = byteArrayOf(0x60, 0x02, 0x00, 0x00, 0x00)
        val completeResponse = try {
            nfcA.transceive(completeCommand)
        } catch (ioe: IOException) {
            Log.e(TAG, "complete transceive failed", ioe)
            return@withContext false
        }
        completeResponse.size == 6 &&
            completeResponse[0] == 0x60.toByte() &&
            completeResponse[1] == 0x60.toByte() &&
            completeResponse[2] == 0x00.toByte() &&
            completeResponse[3] == 0x00.toByte() &&
            completeResponse[4] == (fnIndex and 0xFF).toByte() &&
            completeResponse[5] == 0x60.toByte()
    }
}
